#if defined(_MSC_VER)
#include "SDL.h"
#else
#include "SDL/SDL.h"
#endif


#include "BaseGameEngine.h"
#include "DisplayableObject.h"


/**
Constructor.
*/
BaseGameEngine::BaseGameEngine(int iMaxObjects)
: m_pKeyStatus(NULL)
, m_iExitWithCode(-1)
, m_iTick(0)
, m_bNeedsRedraw(true)
, m_bWholeScreenUpdate(true)
, m_iMaxObjects(iMaxObjects+2/*Add 2 leeway so we know we always have at least 2*/)
, m_iNextTickToActOn(0)
, m_ppDisplayableObjects(NULL)
{
	m_pUpdateRectangles = new SDL_Rect[m_iMaxObjects];
	m_iUpdateRectsInUse = 0;
}


/**
Destructor - destroys moving objects
*/
BaseGameEngine::~BaseGameEngine(void)
{
	DestroyOldObjects();
}

/** Destroy any existing displayable objects */
void BaseGameEngine::DestroyOldObjects()
{
	if ( m_ppDisplayableObjects != NULL )
	{
		DisplayableObject** ppTemp = m_ppDisplayableObjects;
 		while ( *ppTemp )
		{
			delete *ppTemp;
			ppTemp++;
		}
		delete[] m_ppDisplayableObjects;
		m_ppDisplayableObjects = NULL;
	}
}



/**
Initialise SDL and create a window of the correct size.
*/ 
int BaseGameEngine::Initialise( int iScreenWidth, int iScreenHeight )
{
	m_iScreenWidth = iScreenWidth;
	m_iScreenHeight = iScreenHeight;

	// Initialize SDL's subsystems
	if (SDL_Init(SDL_INIT_VIDEO) < 0) 
	{
		fprintf(stderr, "Unable to init SDL: %s\n", SDL_GetError());
		return 1;
	}

	// Register SDL_Quit to be called at exit; makes sure things are
	// cleaned up when we quit.
	atexit(SDL_Quit);

	// make sure our deinit gets called at shutdown.
	//atexit(deinit);

	// Attempt to create a m_iScreenWidth x m_iScreenHeight window with 32bit pixels.
	m_pActualScreen = SDL_SetVideoMode(m_iScreenWidth, m_iScreenHeight, 32, SDL_SWSURFACE);
//	m_pActualScreen = SDL_SetVideoMode(m_iScreenWidth, m_iScreenHeight, 32, SDL_HWSURFACE);
//	m_pActualScreen = SDL_SetVideoMode(m_iScreenWidth, m_iScreenHeight, 32, SDL_DOUBLEBUF);

	// If we fail, return error.
	if (m_pActualScreen == NULL) 
	{
		fprintf(stderr, "Unable to set up video: %s\n", SDL_GetError());
		return 2;
	}

	// Retrieve the number of ints per row from the screen information
	m_iIntsPerScreenRow = m_pActualScreen->pitch/sizeof(unsigned int);

	m_puiScreenBuffer = new unsigned int [ m_iIntsPerScreenRow * m_iScreenHeight ];

	// Initialise key status
	m_pKeyStatus = new int[1 + SDLK_LAST - SDLK_FIRST];
	memset( m_pKeyStatus, 0, sizeof(int)*(1 + SDLK_LAST - SDLK_FIRST) );

	// Create a new font object ( deleted in CleanUp() )
	m_pFont = new Font();

	// Load the font from the bmp file.
	m_pFont->Load( "font.bmp", m_pActualScreen->format );

	// Call method to allow custom sub-class initialiation.
	GameInit();

	return 0; // success
}


/*
Initialise game-specific things.
Base class just calls InitialiseObjects() to create the moving objects
*/
int BaseGameEngine::GameInit(void)
{
	// Create the moving objects
	InitialiseObjects();
	// Set up the initial background
	SetupBackgroundBuffer();

	return 0; // success
}


/**
In here you need to create any movable objects that you wish to use.
Sub-classes need to implement this function.
*/
int BaseGameEngine::InitialiseObjects()
{
	// Destroy any existing objects
	DestroyOldObjects();

	// Create an array one element larger than the number of objects that you want.
	// e.g.: m_ppDisplayableObjects = new DisplayableObject*[MAXNUMBER+1];
	
	// You MUST set the array entry after the last one that you create to NULL, so that the system knows when to stop.
	//e.g. m_ppDisplayableObjects[0] = new MyDisplayableObject( this, m_iPlayer1StartX, m_iPlayer1StartY);
	//e.g. m_ppDisplayableObjects[1] = new MyDisplayableObject( this, m_iPlayer2StartX, m_iPlayer2StartY);

	// i.e. The LAST entry has to be NULL. The fact that it is NULL is used in order to work out where the end of the array is.
	//e.g. m_ppDisplayableObjects[2] = NULL;

	// You should return the number of objects which you created.
	// e.g. return 2;

	return 0;
}



/**
Main loop.
1) Stores key presses, and calls call-back functions for press/release if necessary.
2) Calls GameAction
3) Calls GameRender
*/
int BaseGameEngine::MainLoop(void)
{
    SDL_Event event;
	int iKeyCode;

	// Main loop: loop until told not to
	while (m_iExitWithCode==-1)
	{
		// Render stuff
		m_iTick = SDL_GetTicks();

		// Poll for events, and handle the ones we care about.
		while (SDL_PollEvent(&event)) 
		{
			switch (event.type) 
			{
			case SDL_KEYDOWN:
				iKeyCode = event.key.keysym.sym;
				if ( (iKeyCode >= SDLK_FIRST) && (iKeyCode <= SDLK_LAST) )
					m_pKeyStatus[iKeyCode-SDLK_FIRST] = 1;
				KeyDown(iKeyCode);
				break;

			case SDL_KEYUP:                  
				iKeyCode = event.key.keysym.sym;
				if ( (iKeyCode >= SDLK_FIRST) && (iKeyCode <= SDLK_LAST) )
				{
					m_pKeyStatus[iKeyCode-SDLK_FIRST] = 0;
				}
				KeyUp(iKeyCode);
				break;

			case SDL_QUIT:
				return 0;
			}
		}

		// Do any updates for movements
		GameAction();

		// Render the screen
		GameRender();
	}
	return 0;
}

/** Deinitialise everything - delete all resources */
void BaseGameEngine::Deinitialise(void)
{
	// Call method to allow custom sub-class cleanup
	CleanUp();

	// Delete the font - which will clear up the allocated surface.
	delete m_pFont;

	// Free memory for key status record
	delete[] m_pKeyStatus;

	// Free memory for the screen background buffer
	delete[] m_puiScreenBuffer;
}


/**
Overridable function, for adding custom clean-up in sub-classes.
*/
void BaseGameEngine::CleanUp(void)
{
}


/** Copy all of the background (e.g. tiles) to the screen display. e.g. removing any object drawn on top. */
void BaseGameEngine::CopyAllBackgroundBuffer()
{
	memcpy( m_pActualScreen->pixels, m_puiScreenBuffer, 
			sizeof(unsigned int) * m_iIntsPerScreenRow * m_iScreenHeight );
}

/** Copy some of the background onto the screen, e.g. removing an object which was drawn on top. */
void BaseGameEngine::CopyBackgroundPixels( int iX, int iY, int iWidth, int iHeight )
{
	int iStart = iY * m_iIntsPerScreenRow + iX;
	int iIncrement = m_iIntsPerScreenRow - iWidth;
	unsigned int * puiSource = m_puiScreenBuffer + iStart;
	unsigned int * puiDest = ((unsigned int *)m_pActualScreen->pixels) + iStart;
	for ( int i = 0 ; i < iHeight ; i++ )
	{
		// Copy a line
		for ( int j = 0 ; j < iWidth ; j++ )
			*puiDest++ = *puiSource++;
		// Align on the next line
		puiSource += iIncrement;
		puiDest += iIncrement;
	}
}




int BaseGameEngine::IsKeyPressed(int iKeyCode)
{
	if ( (iKeyCode >= SDLK_FIRST) && (iKeyCode <= SDLK_LAST) )
		return m_pKeyStatus[iKeyCode-SDLK_FIRST];
	// Otherwise it was an invalid key code, so just say it wasn't pressed
	return 0;
}


// Default bahaviour is to end program when esc is pressed
void BaseGameEngine::KeyUp(int iKeyCode)
{
}

void BaseGameEngine::KeyDown(int iKeyCode)
{
	bool m_bPaused;
}


/**
Internal function.
Get a pointer to the next update rectangle to use.
*/
SDL_Rect* BaseGameEngine::GetNextUpdateRect()
{
	if ( m_iUpdateRectsInUse >= (m_iMaxObjects-1) )
	{
		// If the caller tried to use more than are available then just refresh the full screen
		m_iUpdateRectsInUse = m_iMaxObjects;
		return m_pUpdateRectangles;
	}
	else
	{
		m_iUpdateRectsInUse++;
		//printf( "Providing update rect %d\n", m_iUpdateRectsInUse );
		return m_pUpdateRectangles + (m_iUpdateRectsInUse -1);
	}
}
















/** Sub-class should actually do something, if relevant
e.g. move the player or any other sprites */
void BaseGameEngine::GameAction()
{
	// If too early to act then do nothing
	if ( !TimeToAct() )
		return;

	// Don't act for another 10 ticks
	SetTimeToAct( 10 );

	UpdateAllObjects( GetTime() );
}


/** Tell all displayable objects to update themselves. Calls DoUpdate on each displayable object. */
void BaseGameEngine::UpdateAllObjects( int iCurrentTime )
{
	if ( m_ppDisplayableObjects != NULL )
	{
		DisplayableObject** pTemp = m_ppDisplayableObjects;
		while ( *pTemp )
		{
			(*pTemp)->DoUpdate(iCurrentTime);
			pTemp++;
		}
	}
}





/**
Overridable function, if necessary, for doing all of the drawing.
Base class version checks whether a redraw is needed and does nothing if not. See Redraw() function.
If a redraw is needed then there is an option to either redraw the whole screen or just redraw the moving objects.
If redrawing the whole screen then:
1a) SetupBackgroundBuffer is called to draw the background to the background buffer.
1b) DrawScreen is called to copy the background buffer onto the screen, then draw the moving objects.
1c) SDL_UpdateRect() is called to actually redraw the whole screen.
If only redrawing the moving objects then:
2a) DrawChangingObjects() is called to remove the objects from their old positions, and redraw them in their new positions.
2b) GetUpdateRectanglesForChangingObjects() is called to calculate where on the screen needs redrawing.
2c) SDL_UpdateRects() is called to redraw the parts of the screen which changed.
*/
void BaseGameEngine::GameRender(void)
{
	if ( !m_bNeedsRedraw )
		return;

	// Just drawn so no more redraw needed
	m_bNeedsRedraw = false;
	// Note: the redraw flags must only be set early in this method, not at the end, since the drawing/moving of the moving objects may
	// potentially flag a win/lose condition and a state change, which will set the redraw flag again to force a full draw after the 
	// state changes.

	if ( m_bWholeScreenUpdate )
	{
		// Drawn whole screen now, so no need to draw it again. See note above about the importance of not resetting this after DrawChangingObjects()
		m_bWholeScreenUpdate = false;

		// Lock surface if needed
		if (SDL_MUSTLOCK(m_pActualScreen))
			if (SDL_LockSurface(m_pActualScreen) < 0) 
				return;

		// Draw the screen as it should appear now.
		DrawScreen();

		// Unlock if needed
		if (SDL_MUSTLOCK(m_pActualScreen)) 
			SDL_UnlockSurface(m_pActualScreen);

		SDL_UpdateRect(m_pActualScreen, 0, 0, m_iScreenWidth, m_iScreenHeight);    
	}
	else
	{
		// Here we assume that the background buffer has already been set up

		// Lock surface if needed
		if (SDL_MUSTLOCK(m_pActualScreen))
			if (SDL_LockSurface(m_pActualScreen) < 0) 
				return;

		// Remove objects from their old positions
		UndrawChangingObjects();
		// Draw objects at their new positions
		DrawChangingObjects();
		// Draw the text for the user
		DrawStrings();

		// Unlock if needed
		if (SDL_MUSTLOCK(m_pActualScreen)) 
			SDL_UnlockSurface(m_pActualScreen);

		// Work out which parts of the screen need to be updated
		GetUpdateRectanglesForChangingObjects();

		// Now actually perform the updates
		if ( m_iUpdateRectsInUse >= m_iMaxObjects )
		{
			//printf( "Update whole window\n" );
			//SDL_UpdateRect(m_pActualScreen, 0, 0, m_iScreenWidth, m_iScreenHeight);    
			SDL_UpdateRect(m_pActualScreen, 0, 0, 0, 0);    
		}
		else
		{
			//printf( "Update %d rectangles\n", m_iUpdateRectsInUse );
			SDL_UpdateRects( m_pActualScreen, m_iUpdateRectsInUse, m_pUpdateRectangles );
		}
		// DEBUG
		//printf( "%d update rectangles found\n", m_iUpdateRectsInUse );
		//for ( int i = 0 ; i < m_iUpdateRectsInUse ; i++ )
		//	printf( "\tNumber %d : %d, %d, %d, %d\n", i, m_pUpdateRectangles[i].x, m_pUpdateRectangles[i].y, m_pUpdateRectangles[i].w, m_pUpdateRectangles[i].h );
		// End of debug
		m_iUpdateRectsInUse = 0;
	}

}





/** Fill the background - should be overridden to actually draw the correct thing. */
void BaseGameEngine::SetupBackgroundBuffer()
{
	// Put any special code to call different render functions for different states here
	FillBackground( 0xffff00 );
}

/** Fill the background with a specific colour */
void BaseGameEngine::FillBackground(unsigned int uiPixelColour)
{
	int iStart = 0;
	unsigned int * puiDest = m_puiScreenBuffer;
	for ( int i = 0 ; i < (m_iScreenHeight * m_iIntsPerScreenRow) ; i++ )
		*puiDest++ = uiPixelColour;
}

/** Draw the screen - copy the background buffer, then draw the text and objects. */
void BaseGameEngine::DrawScreen()
{
	// First draw the background
	CopyAllBackgroundBuffer();
	// Then draw the changing objects
	DrawChangingObjects();
	// And finally, draw the text
	DrawStrings();
}


/** Draw text labels */
void BaseGameEngine::DrawStrings()
{
	// First clear the area where they were originally drawn - i.e. undraw previous text
	// e.g. CopyBackgroundBuffer()
	// Then draw the new labels/strings
	//e.g. m_pFont->DrawString(m_pActualScreen, 100, 180, "The DrawScreen method needs to be overridden", 0xff00ff);
}







/**
Asks each displayable object in turn to calculate its redraw rectangle.
This needs to cover both the old position and new position for the object.
This area will be updated on the screen - i.e. remove the background from the old position and draw the object in the new position.
*/
void BaseGameEngine::GetUpdateRectanglesForChangingObjects()
{
	SDL_Rect* pRect;
	
	if ( m_ppDisplayableObjects != NULL )
	{
		DisplayableObject** ppTemp = m_ppDisplayableObjects;
		while ( *ppTemp )
		{
			pRect = GetNextUpdateRect();
			(*ppTemp)->GetRedrawRect( pRect );
			ppTemp++;
		}
	}
/*
	// Update the entire screen by default
	SDL_Rect* pRect = GetNextUpdateRect();
	pRect->x = 0;
	pRect->y = 0;
	pRect->w = m_iScreenWidth;
	pRect->h = m_iScreenHeight;
*/
}

/**
Redraws the background behind each of the displayable objects, in their old positions.
*/
void BaseGameEngine::UndrawChangingObjects()
{
	// This effectively un-draws the old positions of the objects
	if ( m_ppDisplayableObjects != NULL )
	{
		DisplayableObject** ppTemp = m_ppDisplayableObjects;
		while ( *ppTemp )
		{
			(*ppTemp)->RedrawBackground();
			ppTemp++;
		}
	}
}

/**
Draws draws the displayable objects in their new positions.
*/
void BaseGameEngine::DrawChangingObjects()
{
	// And this re-draws the new positions
	if ( m_ppDisplayableObjects != NULL )
	{
		DisplayableObject** ppTemp = m_ppDisplayableObjects;
		while ( *ppTemp )
		{
			(*ppTemp)->Draw();
			ppTemp++;
		}
	}
}





/**
Gets the specified displayable object.
You may need to dynamic_cast the resulting pointer to the correct type.
*/
DisplayableObject* BaseGameEngine::GetDisplayableObject( int iIndex )
{
	if ( m_ppDisplayableObjects != NULL )
	{
		return m_ppDisplayableObjects[iIndex];
	}
	else
		return NULL;
}
